﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MbUnit.Framework;
using FlowBasis.EntityFramework;
using System.Data.Metadata.Edm;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using FlowBasis.Data;
using FlowBasisUnitTests.EntityFramework.SampleDomain;

namespace FlowBasisUnitTests.EntityFramework
{
    public class EntityFrameworkWrapperTests
    {
        private SampleDbContext sampleDbContext;
        private string connectionString;

        public EntityFrameworkWrapperTests()
        {
            string sampleDbPath = FlowBasis.Util.AssemblyHelper.GetAssemblyDirectory(typeof(EntityFrameworkMetadataServiceTests).Assembly);
            sampleDbPath += @"\SampleDb.sdf";

            this.connectionString = "Data Source=" + sampleDbPath;
            
            Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
            Database.SetInitializer(new DropCreateDatabaseAlways<SampleDbContext>());

            this.sampleDbContext = new SampleDbContext(this.connectionString);
        }

        [Test]
        public void Test_Insert_And_Retrieve_Author()
        {
            Func<SampleDbContext> dbContextFactory = () => new SampleDbContext(this.connectionString);
            EntityFrameworkMetadataService metadataService = new EntityFrameworkMetadataService(
                dbContextFactory, new Type[] { typeof(Author), typeof(Book) });
            EntityFrameworkPrimaryKeyService primaryKeyService = new EntityFrameworkPrimaryKeyService(metadataService);
            
            IUnitOfWork unitOfWork = new EntityFrameworkUnitOfWork(() => new SampleDbContext(this.connectionString));

            var author1 = unitOfWork.New<Author>();
            author1.FirstName = "Bob";
            author1.LastName = "Smith";
            unitOfWork.Insert(author1);
            unitOfWork.Commit();

            var author2 = unitOfWork.New<Author>();
            author2.FirstName = "Alice";
            author2.LastName = "Johnshon";
            unitOfWork.Insert(author2);
            unitOfWork.Commit();

            Assert.AreNotEqual(0, author1.AuthorId);
            Assert.AreNotEqual(0, author2.AuthorId);

            IDataService dataService = new EntityFrameworkDataService(this.sampleDbContext, primaryKeyService);

            var resultAuthor1 = dataService.Find<Author>(author1.AuthorId);
            Assert.AreEqual("Bob", resultAuthor1.FirstName);
            
            var resultAuthor2 = dataService.Find<Author>(author2.AuthorId);
            Assert.AreEqual("Alice", resultAuthor2.FirstName);
        }

        [Test]
        public void Test_Insert_And_Retrieve_Author_With_Books_And_Delete_A_Book_Too()
        {
            Func<SampleDbContext> dbContextFactory = () => new SampleDbContext(this.connectionString);

            EntityFrameworkMetadataService metadataService = new EntityFrameworkMetadataService(
                dbContextFactory, new Type[] { typeof(Author), typeof(Book) });
            EntityFrameworkPrimaryKeyService primaryKeyService = new EntityFrameworkPrimaryKeyService(metadataService);

            EntityFrameworkDataService dataService = new EntityFrameworkDataService(dbContextFactory(), primaryKeyService);

            EntityFrameworkUnitOfWork unitOfWork = new EntityFrameworkUnitOfWork(dbContextFactory);

            var author = unitOfWork.New<Author>();
            author.FirstName = "James";
            author.LastName = "Jennings";
            unitOfWork.Insert(author);

            var book1 = unitOfWork.New<Book>();
            book1.Title = "SomeBook";
            author.Books.Add(book1);

            var book2 = unitOfWork.New<Book>();
            book2.Title = "AnotherBook";
            author.Books.Add(book2);

            var book3 = unitOfWork.New<Book>();
            book3.Title = "YetAnotherBook";
            author.Books.Add(book3);

            unitOfWork.Commit();
            unitOfWork.Dispose();

            // Verify we get all three books.

            var resultAuthor = dataService.Find<Author>(author.AuthorId);            
            Assert.AreEqual("James", resultAuthor.FirstName);

            Assert.AreEqual(3, resultAuthor.Books.Count);

            var books = resultAuthor.Books.OrderBy(b => b.Title).ToList();
            Assert.AreEqual("AnotherBook", books[0].Title);
            Assert.AreEqual("Jennings", books[0].Author.LastName);
            Assert.AreEqual("SomeBook", books[1].Title);
            Assert.AreEqual("YetAnotherBook", books[2].Title);

            // Delete one of the books and change first name.

            unitOfWork = new EntityFrameworkUnitOfWork(dbContextFactory);

            resultAuthor = unitOfWork.Find<Author>(author.AuthorId);
            resultAuthor.FirstName = "Jim";
            resultAuthor.Books.Remove(resultAuthor.Books.Single(b => b.Title == "SomeBook"));            
            unitOfWork.Commit();
            unitOfWork.Dispose();

            // Verify we now only have two books for the author.

            resultAuthor = dataService.Find<Author>(author.AuthorId);
            Assert.AreEqual("Jim", resultAuthor.FirstName);

            Assert.AreEqual(2, resultAuthor.Books.Count);

            books = resultAuthor.Books.OrderBy(b => b.Title).ToList();
            Assert.AreEqual("AnotherBook", books[0].Title);
            Assert.AreEqual("Jennings", books[0].Author.LastName);            
            Assert.AreEqual("YetAnotherBook", books[1].Title);

            // SomeBook should still exist with a null AuthorId.
            var someBook = dataService.GetAll<Book>().SingleOrDefault(b => b.Title == "SomeBook");
            Assert.IsNotNull(someBook);

            // We should also be able to retrieve Author with the non-generic Find method.
            resultAuthor = (Author)dataService.Find(typeof(Author), author.AuthorId);
            Assert.AreEqual("Jim", resultAuthor.FirstName);

            dataService.Dispose();
        }
    }
}
